package com.yunji.system.config.mybatis;

import com.google.common.base.Joiner;
import com.yunji.common.annotation.DataScopeThread;
import com.yunji.common.base.BaseEnum;
import com.yunji.common.base.SqlConcatParamThread;
import com.yunji.common.constant.PermissionTypeConstant;
import com.yunji.common.enums.common.PermissionTypeEnum;
import com.yunji.common.enums.common.UserType;
import com.yunji.common.model.dto.LoginInfo;
import com.yunji.common.model.dto.SysRoleDTO;
import com.yunji.common.utils.StringUtils;
import com.yunji.system.aspectj.SqlConditionAspect;
import com.yunji.system.context.SystemContext;
import net.sf.jsqlparser.statement.select.PlainSelect;
import org.apache.commons.compress.utils.Lists;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import java.sql.Connection;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Properties;

/**
 * @author : peter-zhu
 * @date : 2024/11/21 17:05
 * @description : TODO
 **/
@Component
@Intercepts({
        // @Signature(type = StatementHandler.class, method = "query", args = {Statement.class, ResultHandler.class})
        @Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})
})
public class SqlConditionIntercepts implements Interceptor {

    private static final Logger logs = LoggerFactory.getLogger(SqlConditionIntercepts.class);

    private static final String MAPPING_STATEMENT = "delegate.mappedStatement";

    private static final String MODIFY_BOUND_SQL = "delegate.boundSql";

    /**
     * 全部数据权限
     */
    public static final String DATA_SCOPE_ALL = "1";
    /**
     * 自定数据权限
     */
    public static final String DATA_SCOPE_CUSTOM = "2";
    /**
     * 部门数据权限
     */
    public static final String DATA_SCOPE_DEPT = "3";
    /**
     * 部门及以下数据权限
     */
    public static final String DATA_SCOPE_DEPT_AND_CHILD = "4";
    /**
     * 仅本人数据权限
     */
    public static final String DATA_SCOPE_SELF = "5";

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        try {
            try {
                Object target = invocation.getTarget();
                StatementHandler handler = (StatementHandler) target;
                MetaObject mObject = SystemMetaObject.forObject(target);
                MappedStatement mappedStatement = (MappedStatement) mObject.getValue(MAPPING_STATEMENT);
                // 执行的mapper statement ID
                String mapper = mappedStatement.getId();
                logs.trace("mapper-----------------{}", mapper);
                if (mappedStatement.getSqlCommandType() == SqlCommandType.SELECT)
                    handleQuery(handler, mappedStatement, mObject);

            } catch (Exception e) {
                throw new RuntimeException(e);
            }
            return invocation.proceed();
        } finally {
            logs.trace("统一拦截查询sql");
            //logs.debug("=====>【{}】 at 【{}】 Begin Execute SQL: {}", id);
        }
    }

    private void handleQuery(StatementHandler handler, MappedStatement mappedStatement, MetaObject mObject) throws Exception {
        DataScopeThread dataScopeThread = SqlConditionAspect.DATA_SCOPE_THREAD.get();
        SqlConcatParamThread sqlConcatParamThread = SqlConditionAspect.SQL_PARAM_THREAD.get();
        if (dataScopeThread == null && sqlConcatParamThread == null)
            return;
        // 获取当前的用户
        LoginInfo currentUser = SystemContext.getUser();
        if (currentUser == null || CollectionUtils.isEmpty(currentUser.getRoles()))
            return;
        List<SysRoleDTO> roles = currentUser.getRoles();
        SysRoleDTO sysRole = roles.stream().findFirst().orElse(null);
        String dataScope = sysRole.getDataScope();
        if (dataScope == null)
            return;
        BoundSql boundSql = handler.getBoundSql();
        String sql = boundSql.getSql();
        logs.trace("originSql---------------{}", sql);
        //Map<String, String> tableMap = SqlParseEngine.tableInfo.getTableMap(sql);
        //logs.debug("tableMap--------------{}", tableMap);
        String modifySql = sql;
        boolean isModify = false;
        // 自定义数据权限可能不止一种，是在注解里配置
        switch (dataScope) {
            case DATA_SCOPE_ALL:
                break;
            case DATA_SCOPE_CUSTOM:
                logs.trace("current data scope------------------------------:{}", dataScopeThread);
                if (dataScopeThread != null) {
                    String[] permissionTypes = dataScopeThread.getPermissionTypes();
                    for (String permissionType : permissionTypes) {
                        PermissionTypeEnum baseEnum = BaseEnum.getBaseEnum(PermissionTypeEnum.class, permissionType);
                        if (baseEnum == null)
                            continue;
                        logs.trace("permissionType----------{}", permissionType);
                        List<SqlParseConcat> concatList = new ArrayList<>();
//                        SqlParseConcat concatSql = null;
                        //FIXME 如果自定义权限过多可以改为策略模式
                        if (PermissionTypeConstant.DATA_SCOPE_BELONG.equals(baseEnum.getCode())
                                || PermissionTypeConstant.DATA_SCOPE_STORAGE_TWO.equals(baseEnum.getCode())
                                || PermissionTypeConstant.DATA_SCOPE_STORAGE_THREE.equals(baseEnum.getCode())
                                || PermissionTypeConstant.DATA_SCOPE_BELONG_IDS.equals(baseEnum.getCode())
                        ) {
                            concatList.addAll(Objects.requireNonNull(getBelongSql(currentUser.getBelongId(), currentUser.getUserType(), baseEnum.getColumns())));
                        }
                        if (!CollectionUtils.isEmpty(concatList)) {
                            SqlParseEngine build = SqlParseEngine.builder(dataScopeThread).concat(concatList).build();
                            PlainSelect plainSelect = build.addConditionToSql(modifySql);
                            modifySql = plainSelect.toString();
                            isModify = true;
                        }
                    }
                }
                logs.trace("modifySql------------{}", modifySql);
                break;
            case DATA_SCOPE_DEPT:
                logs.trace("部门数据权限");
                break;
            case DATA_SCOPE_DEPT_AND_CHILD:
                logs.trace("部门及以下数据权限");
                break;
            case DATA_SCOPE_SELF:
                logs.trace("仅本人数据权限");
                break;
        }
        if (sqlConcatParamThread != null) {
            List<PermissionTypeEnum> permissionTypeList = sqlConcatParamThread.getPermissionTypeList();
            List<String> columns = Lists.newArrayList();
            for (PermissionTypeEnum pte : permissionTypeList) {
                columns.addAll(pte.getColumns());
            }
            List<SqlParseConcat> concatList = buildIdListCondition(sqlConcatParamThread.getParam(), columns);
            if (concatList != null) {
                SqlParseEngine build = SqlParseEngine.builder(sqlConcatParamThread).concat(concatList).build();
                PlainSelect plainSelect = build.addConditionToSql(modifySql);
                modifySql = plainSelect.toString();
                isModify = true;
            }
        }
        if (isModify) {
            BoundSql newBoundSql = new BoundSql(mappedStatement.getConfiguration(), modifySql,
                    boundSql.getParameterMappings(), boundSql.getParameterObject());
            System.out.println("最终执行sql:" + newBoundSql.getSql());
            mObject.setValue(MODIFY_BOUND_SQL, newBoundSql);
        }
    }

    private static List<SqlParseConcat> getBelongSql(Long belongId, String userType, List<String> columnList) {
        if (StringUtils.isEmpty(userType) || belongId == null)
            return null;
        List<SqlParseConcat> list = new ArrayList<>();
        for (String column : columnList) {
            if (UserType.AGENT.getCode().equals(userType)) {
                String concatSql = " exists (select 1 from v3_belong where parent_id=" + belongId + " and belong_id={0}." + column + ")";
                list.add(new SqlParseConcat(column, concatSql, "or"));
            }
            if (UserType.SITE.getCode().equals(userType)) {
                String concatSql = " {0}." + column + "=" + belongId;
                list.add(new SqlParseConcat(column, concatSql, "or"));
            }
        }
        return list;
    }

    private static List<SqlParseConcat> buildIdListCondition(Object objs, List<String> columnList) {
        if (objs instanceof ArrayList<?>) {
            List<?> objIds = (List<?>) objs;
            if (CollectionUtils.isEmpty(objIds)) {
                return null;
            }
            List<SqlParseConcat> list = new ArrayList<>();
            for (String column : columnList) {
                String concatSql = " {0}." + column + " in (" + Joiner.on(',').join(objIds) + ")";
                list.add(new SqlParseConcat(column, concatSql, "or"));
            }
            return list;
        }
        return null;
    }

    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {
        Interceptor.super.setProperties(properties);
    }
}
